Skip to content

fix(publish): resolve catalog:/workspace:* protocols + use npm pack#73

Merged
byapparov merged 1 commit into
mainfrom
72-fix-publish-protocols
May 21, 2026
Merged

fix(publish): resolve catalog:/workspace:* protocols + use npm pack#73
byapparov merged 1 commit into
mainfrom
72-fix-publish-protocols

Conversation

@byapparov
Copy link
Copy Markdown
Contributor

Summary

The publish pipeline shipped `@aictrl/cli@0.3.3` with unresolved `workspace:*` deps and `@aictrl/util@1.2.16` with unresolved `catalog:` deps. Both are Bun-only protocols — plain `npm install` rejects them with `EUNSUPPORTEDPROTOCOL`. This silently broke the AI Review workflow on every PR for ~4 weeks (#72) and any downstream npm consumer.

Root cause

Two interacting bugs:

  1. `bun pm pack` ignores in-place package.json edits. `publish.yml`'s "Strip bundled deps" step ran a `node -e ...` that deleted `pkg.dependencies`, `pkg.devDependencies`, `pkg.peerDependencies`, `pkg.exports`, and `pkg.type` before pack — but every one of those fields survived to the published tarball. Verified by extracting `aictrl-cli-0.3.3.tgz` and inspecting its `package.json`: `type`, `exports`, `dependencies` (with `workspace:*`), `devDependencies`, `peerDependencies` all present. `bun pm pack` reads workspace deps from the workspace lockfile/manifest snapshot, not from the on-disk `package.json`.

  2. No `catalog:` resolver anywhere in the pipeline. `@aictrl/util` ships `{ zod: 'catalog:' }` because the strip-everything approach (only run on CLI) doesn't apply to packages that actually need runtime deps. The `catalog:` protocol comes from Bun's centralized version pinning; npm has no equivalent and rejects it.

Evidence:

```bash
$ npm view @aictrl/cli@0.3.3 dependencies
{ '@aictrl/plugin': 'workspace:', '@aictrl/sdk': 'workspace:', '@aictrl/util': 'workspace:*', ... }

$ npm view @aictrl/util@latest dependencies
{ zod: 'catalog:' }

$ npm install @aictrl/cli@latest
npm error code EUNSUPPORTEDPROTOCOL
npm error Unsupported URL Type "workspace:": workspace:*
```

Fix

`publish-if-new.sh`

  • Add a catalog:/workspace: resolver before pack. Reads root `package.json > workspaces.catalog` for catalog deps, uses `AICTRL_VERSION` env for sibling workspace deps. Covers `dependencies`, `optionalDependencies`, `peerDependencies`, `devDependencies` fields.
  • Fail-loud if a catalog dep has no catalog entry, or if `workspace:*` is hit without `AICTRL_VERSION`. No silent fallback.
  • Replace `bun pm pack` with `npm pack` — `npm pack` reads only the on-disk `package.json`, so both the resolver above and the existing "Strip bundled deps" step in `publish.yml` actually take effect.

`publish.yml`

  • In Build Packages, expose stripped `AICTRL_VERSION` (no `v` prefix) to subsequent steps via `$GITHUB_ENV` so the resolver can see it. One-line change.
  • New post-publish smoke test step: in a scratch dir on the runner, `npm install @aictrl/cli@$AICTRL_VERSION`, with CDN-aware retry (5 × 30s). Fails loud on `EUNSUPPORTEDPROTOCOL` immediately (no retry — won't help). Catches the entire bug class.

Local verification

Resolver against `packages/util/package.json`:
```
"zod": "catalog:" → "zod": "4.1.8" ✓
"typescript": "catalog:" → "typescript": "5.8.2" ✓
"@types/bun": "catalog:" → "@types/bun": "1.3.9" ✓
```

Resolver against `v0.3.3:packages/cli/package.json` with `AICTRL_VERSION=0.3.4`:
```
"@aictrl/plugin": "workspace:" → "0.3.4" ✓
"@aictrl/sdk": "workspace:
" → "0.3.4" ✓
"@aictrl/util": "workspace:*" → "0.3.4" ✓
"@octokit/rest": "catalog:" → resolved from root catalog ✓
```
`npm pack` produced a valid `aictrl-cli-0.3.3.tgz`.

Fail-loud path verified: with `AICTRL_VERSION` unset on a CLI package.json that still has workspace deps, the resolver exits with `::error::dependencies[@aictrl/plugin] is workspace:* but AICTRL_VERSION env var is not set` and no tarball is produced.

End-to-end validation plan

The publish workflow only runs on `release: published` events, so this can't be exercised in CI on the PR itself. Validation steps after merge:

  1. Cut a release `v0.3.4` (just for this fix).
  2. Watch the workflow logs:
    • `::notice::resolved Bun-only protocol deps in @aictrl/util@1.2.16` should appear.
    • Smoke test step should print `::notice::npm install @aictrl/cli@0.3.4 succeeded on attempt N` and exit green.
  3. From any clean dir: `mkdir /tmp/t && cd /tmp/t && npm init -y && npm install @aictrl/cli@0.3.4` — should succeed.
  4. (Recommended) `npm deprecate @aictrl/cli@0.3.3 "Broken: workspace:* deps, use 0.3.4+. See Publish: @aictrl/cli@0.3.3 ships unresolved workspace:* deps → npm install fails with EUNSUPPORTEDPROTOCOL #72"` and same for `@aictrl/util@1.2.16` so existing `@latest` resolvers get a warning instead of a silent install failure.

Once 0.3.4 is on npm, the `Aictrl AI Review` workflow on this repo (which does `npm install @aictrl/cli@latest`) will start passing again.

Test plan

  • Resolver outputs concrete semver for `catalog:` deps (verified against `packages/util/package.json`)
  • Resolver outputs concrete semver for `workspace:*` deps when `AICTRL_VERSION` is set (verified against v0.3.3 CLI manifest)
  • Resolver fails loud when a catalog dep has no catalog entry
  • Resolver fails loud on `workspace:*` without `AICTRL_VERSION`
  • `npm pack` produces a valid tarball after the resolver runs
  • YAML lint clean on the updated publish.yml
  • Bash syntax clean on the updated publish-if-new.sh
  • End-to-end: smoke test passes on a real release cut (only verifiable on merge + tag)

Related

Closes #72

🤖 Generated with Claude Code

The publish pipeline shipped @aictrl/cli@0.3.3 with unresolved
`workspace:*` deps and @aictrl/util@1.2.16 with unresolved `catalog:`
deps, breaking plain `npm install` with EUNSUPPORTEDPROTOCOL. Both
protocols are Bun-only (also pnpm/yarn for `workspace:`); npm has no
resolver for either.

Root cause was two-part:

1. `bun pm pack` ignores in-place edits to the on-disk package.json and
   re-injects workspace deps from the workspace lockfile. publish.yml's
   "Strip bundled deps" step had no effect on the tarball — type,
   exports, dependencies, devDependencies, peerDependencies all survived
   the strip and shipped to npm verbatim.

2. publish-if-new.sh had no `catalog:` → semver step. @aictrl/util ships
   `{ zod: 'catalog:' }` because the catalog protocol is invisible to
   the existing strip logic (which only ran on CLI).

Fix:

- Add a catalog:/workspace: resolver to publish-if-new.sh that reads
  the root package.json's workspaces.catalog and the AICTRL_VERSION env
  var, substituting concrete semver into all four dep fields before
  packing. Fails loud if a catalog: dep has no catalog entry, or if
  workspace:* is hit without AICTRL_VERSION.

- Replace `bun pm pack` with `npm pack` so on-disk package.json edits
  actually take effect in the tarball.

- Expose stripped AICTRL_VERSION (no v prefix) to subsequent workflow
  steps via $GITHUB_ENV so the resolver can see it.

- Add a post-publish smoke test step that does `npm install
  @aictrl/cli@$VERSION` in a scratch dir on the runner, with CDN-aware
  retry. Fails the release if the published manifest is broken,
  catching this entire bug class going forward.

Local verification:

- Resolver against packages/util/package.json: catalog: → 4.1.8 ✓
- Resolver against v0.3.3 packages/cli/package.json with AICTRL_VERSION=0.3.4:
  all workspace:* and catalog: deps resolved to concrete semver, npm pack
  produced a valid tarball.
- Fail-loud path verified: missing AICTRL_VERSION on workspace:* dep
  exits with explicit error.

Closes #72

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@byapparov byapparov merged commit 77d1f7a into main May 21, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish: @aictrl/cli@0.3.3 ships unresolved workspace:* deps → npm install fails with EUNSUPPORTEDPROTOCOL

1 participant